Skip to content

fix(s3-compat): v0.33.0 — provider-compatibility correctness + frontend desync + injection defense-in-depth#119

Merged
hxddh merged 1 commit into
mainfrom
claude/architecture-code-docs-review-k41dn6
Jul 16, 2026
Merged

fix(s3-compat): v0.33.0 — provider-compatibility correctness + frontend desync + injection defense-in-depth#119
hxddh merged 1 commit into
mainfrom
claude/architecture-code-docs-review-k41dn6

Conversation

@hxddh

@hxddh hxddh commented Jul 16, 2026

Copy link
Copy Markdown
Owner

What changed

The product's core promise — works against AWS and MinIO/Ceph/R2/B2 — got its first dedicated correctness pass, plus two frontend desync fixes and one prompt-injection defense-in-depth line. Found by a fresh three-angle audit (untrusted-data ingestion, S3-compat edge cases, frontend runtime).

🔴 S3 correctness (HIGH)

  • S1 — no bucket policy now reads as "not public", not "unknown". review_bucket_security set the policy verdict only when GetBucketPolicyStatus returned a value; the common case (no policy → NoSuchBucketPolicy) left the combined verdict at "cannot rule out public" even with a clean, readable ACL — and disagreed with the survey path, which already maps it. Absent ≠ unknown.
  • S2 — addressing-style checks no longer lie on IP endpoints (MinIO/Ceph). botocore never virtual-hosts a bare IP, so the "virtual" probe silently sent the identical path-style URL and the tool reported both_work on the most common S3-compatible setup. Now detects the IP and reports path-style with a note.

🟠 S3 correctness (MEDIUM)

  • S6/S9 — rule-18 consistency. list_object_versions/list_multipart_uploads mapped NotImplemented/501 (and now a bare 405) to a hard failure the agent read as "0 versions / 0 uploads". Now provider_unsupported, like the sibling object tools.
  • S4/S5/S7 — paging made real. The agent wrappers dropped the paging markers the S3 layer already accepted, so first-page counts were reported as totals (a >1000-version bucket, a 10,000-part upload). Markers threaded through; list_upload_parts accepts PartNumberMarker; list_buckets follows ContinuationToken (page cap + list_truncated).
  • S3/S10 — evidence listing no longer hides its cap. _list_prefix stopped at 5000 objects with no signal; since log/inventory keys sort chronologically, a recent-window query silently missed the newest logs. Now returns a truncation flag (→ plan warning) + a page-budget guard against empty-token infinite loops.
  • S8 — no false "wrong signing region." A MinIO/Ceph server returning an empty LocationConstraint was normalized to us-east-1 then flagged mismatched against any region label. The check now skips a custom endpoint whose bucket reports no region.

🟠 Frontend

  • F1 — a transient reload blip at turn completion no longer erases the answer. The reload could fail (sidecar GC/restart/network) yet the code unconditionally cleared the streamed bubble, so the answer + user message vanished. reload now reports success; the bubble is kept (marked stalled, with a reload affordance) on a blip.
  • F2 — concurrent same-session reloads guarded by a monotonic token (a stale fetch can't clobber a fresh one).
  • F3 — the health poll clears its abort timer on a fetch rejection, not only on success.

🟢 Security (defense-in-depth)

  • P1 — the agent is told tool-result text is untrusted data. A read-only investigator ingests attacker-influenceable bytes (bucket/object names, previewed bodies, config rules, logs). Structurally these only reach the model via the tool channel (never the system prompt), all tools are read-only, and EXPENSIVE actions are confirmation-gated — so the ceiling is "a wasted turn", not RCE. A safety rule makes it explicit: report on that text, never obey directives inside it.

What checks were run

  • cd sidecar && pytest -q584 passed (+11 in tests/test_v0330_fixes.py; updated the rule-18 versions test).
  • ruff check app — clean.
  • cd frontend && npm run typecheck && npm run build — clean.

What was not run

  • No live MinIO/Ceph/R2 endpoint here — provider behaviors are covered with botocore stubs / fakes; the IP-addressing and empty-LocationConstraint cases were verified against installed botocore semantics during the audit.
  • Frontend F1/F2/F3 verified by types + build (no headless UI harness in this repo).

Known gaps / deferred

  • F4/F5 (toast setTimeout cleanup; a one-frame duplicate-bubble flash) deferred as non-bugs: in React 18 a setState after unmount is a silent no-op, and the flash "almost certainly won't paint."
  • The untrusted-ingestion audit found no high/critical exposure — the structure is sound; P1 is the one worthwhile hardening.

Security invariants

No new writes, no shell, no destructive S3 op. All new/changed S3 tools stay read-only and bounded; provider_unsupported mapping widened (rule 18), never narrowed. Bucket/object names still bypass redaction deliberately (they're reused as Bucket= args) — secrets in error messages/headers are still redacted.

🤖 Generated with Claude Code

https://claude.ai/code/session_01M3YojFPzjkfMj6YYSwvzdx


Generated by Claude Code

…nd desync + injection defense-in-depth

The core promise (works against AWS AND MinIO/Ceph/R2/B2) got its first dedicated
correctness pass, plus two frontend desync fixes and one prompt-injection
defense-in-depth line. Found by a fresh three-angle audit.

S3 correctness & provider compatibility:
- review_bucket_security: no bucket policy (NoSuchBucketPolicy) now reads as
  policy-not-public instead of "unknown", matching the survey path — a clean
  bucket is no longer reported as "cannot rule out public".
- test_addressing_style on an IP endpoint (MinIO/Ceph) no longer falsely reports
  both_work: botocore never virtual-hosts an IP, so it detects the IP and reports
  path-style with a note.
- list_object_versions/list_multipart_uploads map 501/NotImplemented (and a bare
  405) to provider_unsupported, not a hard failure read as "0 versions" (rule 18).
- Paging made real: the agent wrappers thread key/version/upload/part markers the
  S3 layer already accepted (first-page counts were reported as totals);
  list_upload_parts accepts PartNumberMarker; list_buckets follows
  ContinuationToken with a page cap + list_truncated.
- evidence _list_prefix returns a truncation flag (surfaced as a plan warning) +
  a page-budget guard against empty-token infinite loops; a 5000-key silent cap
  had dropped the newest chronologically-sorted logs.
- region_mismatch skips a custom endpoint whose bucket returns an empty
  LocationConstraint (no false "wrong signing region" on MinIO/Ceph).

Frontend:
- A transient reload failure at turn completion no longer erases the streamed
  answer: reload reports success and the bubble is kept (stalled) on a blip.
- Concurrent same-session reloads guarded by a monotonic token (stale can't
  clobber fresh).
- Health poll clears its abort timer on fetch rejection too.

Security (defense-in-depth):
- A safety rule tells the agent that tool-result text (bucket/object names,
  previewed bodies, config rules, logs) is untrusted data, never instructions.
  Structurally sound already (untrusted data only reaches the model via the tool
  channel; all tools read-only; EXPENSIVE ops confirmation-gated) — ceiling is a
  wasted turn, not RCE.

Tests: +11 (test_v0330_fixes.py) + updated the rule-18 versions test. Full
sidecar suite 584 passed; ruff clean; tsc + frontend build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M3YojFPzjkfMj6YYSwvzdx

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 131f2e90ed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread sidecar/app/s3/tools.py
cont = None
truncated = False
for page in range(_MAX_BUCKET_PAGES):
resp = client.list_buckets(**({"ContinuationToken": cont} if cont else {}))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Send MaxBuckets on the first ListBuckets page

When an AWS account has an approved bucket quota above 10,000, the ListBuckets API rejects unpaginated requests; the AWS docs' paginated examples use max-buckets on the initial request. This loop sends no parameters while cont is null, so the first call is still unpaginated and fails before any ContinuationToken can be followed, leaving large accounts unable to list buckets despite the new pagination path. Pass MaxBuckets on every page.

Useful? React with 👍 / 👎.

// still lacks the new messages, so clearing the streamed bubble now
// would erase the answer the user just watched complete. Keep it and
// mark stalled so they get a reload affordance instead (F1).
patchSessionRun(id, { stalled: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear stream state when marking a reload failure stalled

When a successful streamed turn hits a transient reload failure, streamText/streamTools are still populated. Thread.tsx only renders the stalled reload card in the branch where both are empty; with streamed content it stays in the live message branch with streaming={!run.stopped} and no reload button, so the finished answer appears to stream forever. Clear the stream state here or make the render branch honor run.stalled first.

Useful? React with 👍 / 👎.

@hxddh
hxddh merged commit a692fc8 into main Jul 16, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants